/*************************************
*Stephen Durfey
*Jason Lyle
*Program 2
*T Th 1:00-2:15
************************************/
#include <iostream>
#include <stdlib.h>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <cstring>
#include <string>
#include <stdio.h>
#include <unistd.h>
#include "SocketComm.h"
#include "ProcUtil.h"
using namespace std;

void procExecute(ProcUtil, int);
int randNum(int);

int main(int argc, char *argv[])
{
    //Function call should be of the format a.out cmd1
    //given:
    //  cmd1 is the number of messages each process sends
    if(argc != 2){
        cout << "Incorrect number of command line parameters." << endl;
        cout << "Please provide a number that will be the number " <<
        "of messages for each process to send." << endl;
        return 1;
    }

    int cmd1;
    pid_t parent = -1, p1 = -1, p2 = -1, p3 = -1, p4 = -1;
    SocketComm p0_1, p0_2, p0_3, p0_4, p1_2, p2_3, p3_4;

    //Convert the command line parameter from a chracter into an int
    cmd1 = atoi(argv[1]);

    //Create the SocketComm objects for communication
    p0_1 = SocketComm(0, 1);
    p0_2 = SocketComm(0, 2);
    p0_3 = SocketComm(0, 3);
    p0_4 = SocketComm(0, 4);
    p1_2 = SocketComm(1, 2);
    p2_3 = SocketComm(2, 3);
    p3_4 = SocketComm(3, 4);

    //Get the parent process id
    parent = getpid();

    //Create the child processes
    if(getpid() == parent){
        p1 = fork();
    }
    if(getpid() == parent){
        p2 = fork();
    }
    if(getpid() == parent){
        p3 = fork();
    }
    if(getpid() == parent){
        p4 = fork();
    }
    
    //Seed the random number generator
    srand(time(NULL) + parent + p1 + p2 + p2 + p4);

    /************************** Parent Process **************************/
    if(getpid() == parent){
        //Close unused pipes
        p1_2.closeSockets();
        p2_3.closeSockets();
        p3_4.closeSockets();

        //Create ProcUtil object for this instance of the process
        ProcUtil pu = ProcUtil(0);
        pu.addSocket(p0_1);
        pu.addSocket(p0_2);
        pu.addSocket(p0_3);
        pu.addSocket(p0_4);

        procExecute(pu, cmd1);

        //Close remaining pipes
        p0_1.closeSockets();
        p0_2.closeSockets();
        p0_3.closeSockets();
        p0_4.closeSockets();

        return 0;
    }
    /********************************************************************/
    /**************************** Process P1 ****************************/
    else if(p1 == 0){
        //Close unused pipes
        p0_2.closeSockets();
        p0_3.closeSockets();
        p0_4.closeSockets();
        p2_3.closeSockets();
        p3_4.closeSockets();

        //Create ProcUtil object for this instance of the process
        ProcUtil pu = ProcUtil(1);
        pu.addSocket(p0_1);
        pu.addSocket(p1_2);

        procExecute(pu, cmd1);

        //Close remaining pipes
        p0_1.closeSockets();
        p1_2.closeSockets();

        exit(0);
    }
    /********************************************************************/
    /**************************** Process P2 ****************************/
    else if(p2 == 0){
        //Close unused pipes
        p0_1.closeSockets();
        p0_3.closeSockets();
        p0_4.closeSockets();
        p3_4.closeSockets();

        //Create ProcUtil object for this instance of the process
        ProcUtil pu = ProcUtil(2);
        pu.addSocket(p0_2);
        pu.addSocket(p1_2);
        pu.addSocket(p2_3);

        procExecute(pu, cmd1);

        //Close remaining pipes
        p0_2.closeSockets();
        p1_2.closeSockets();
        p2_3.closeSockets();

        exit(0);
    }
    /********************************************************************/
    /**************************** Process P3 ****************************/
    else if(p3 == 0){
        //Close unused pipes
        p0_1.closeSockets();
        p0_2.closeSockets();
        p0_4.closeSockets();
        p1_2.closeSockets();

        //Create ProcUtil object for this instance of the process
        ProcUtil pu = ProcUtil(3);
        pu.addSocket(p0_3);
        pu.addSocket(p2_3);
        pu.addSocket(p3_4);

        procExecute(pu, cmd1);

        //Close remaining pipes
        p0_3.closeSockets();
        p2_3.closeSockets();
        p3_4.closeSockets();

        exit(0);
    }
    /********************************************************************/
    /**************************** Process P4 ****************************/
    else if(p4 == 0){
        //Close unused pipes
        p0_1.closeSockets();
        p0_2.closeSockets();
        p0_3.closeSockets();
        p1_2.closeSockets();
        p2_3.closeSockets();

        //Create ProcUtil object for this instance of the process
        ProcUtil pu = ProcUtil(4);
        pu.addSocket(p0_4);
        pu.addSocket(p3_4);

        procExecute(pu, cmd1);

        //Close remaining pipes
        p0_4.closeSockets();
        p3_4.closeSockets();

        exit(0);
    }
    /********************************************************************/
}


void procExecute(ProcUtil pu, int numMsgsToSend)
{
    int dest, iMsg;
    int cntSent = 0;
    char cMsg[5];
    Frame toSend, recvd;

    while(!pu.isDone())
    {
        //Check to see if this process is done sending and recieving data messages.
        //The process may still redirect messages or send acknowledgements after
        //it is done.
        if(
            !pu.waitingForAck()
            && cntSent == numMsgsToSend
            && !pu.isDone(pu.getAddress())
          )
        {
            pu.setDone(pu.getAddress());

            for(int i = 0; i < numProcs; i++)
            {
                if(i != pu.getAddress())
                {
                    toSend = Frame(pu.getAddress(), i, true, "-1");
                    pu.send(toSend);
                }
            }
        }

        //Check if the process can send a message.  The process can only send a message
        //if it is not currently waiting for an acknowledgement and if it has not
        //already sent the alloted number of messages.
        if(cntSent < numMsgsToSend && !pu.waitingForAck())
        {
            //Randomly get which process to send to.  While the process to send to
            //is the current process, get another process to send to.
            do
            {
                dest = randNum(numProcs-1);
            }
            while(dest == pu.getAddress());

            //Get a random number to send as the message in this frame.  Then convert the
            //message from an into into a character array.
            iMsg = randNum(100);
            sprintf(cMsg, "%d", iMsg);

            //Create and send the frame, and increment the counter keeping track of
            //how many messages this process has sent.
            toSend = Frame(pu.getAddress(), dest, true, cMsg);
            pu.send(toSend);
            cntSent++;
            
            //Print out the information about the sent message
            cout << "Process " << pu.getAddress() << " sent "
            << cMsg << " to process " << dest << endl;
        }

        //Listen for messages on all open pipes for this process.
        recvd = pu.listen();

        //If a message is returned, handle the frame
        if(!Frame::isNullFrame(recvd))
        {
            //If the recieved frame is a data frame handle it appropriately
            if(recvd.getFrameType() == Frame::DATA_FRAME)
            {
                //If the message of the data frame is a -1, then this means
                //that the sender of the message is informing the current
                //process that the sender is done with it's active section
                if(atoi(recvd.getMessage()) == -1)
                {
                    pu.setDone(recvd.getSender());
                }
                //If the message is anything else, then print out the results.
                else
                {
                    cout << "Process " << recvd.getDestinationer()
                    << " received message of " << recvd.getMessage()
                    << " from process " << recvd.getSender() << endl;
                }
            }
        }
    }
}


//Return a random number in the range 0-max inclusive.
int randNum(int max)
{
    return rand() % (max + 1);
}
